package org.jugile.util;

/*

Copyright (C) 2007-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * "And GOD saw that the wickedness of man was great in the earth, and that every 
 *  imagination of the thoughts of his heart was only evil continually. 
 *  And it repented the LORD that he had made man on the earth, 
 *  and it grieved him at his heart. And the LORD said, I will destroy man whom 
 *  I have created from the face of the earth; both man, and beast, and the creeping thing, 
 *  and the fowls of the air; for it repenteth me that I have made them.
 *  But Noah found grace in the eyes of the LORD. These are the generations of Noah: 
 *  Noah was a just man and perfect in his generations, and Noah walked with God." (Gen 6:5-9
 *
 * ==========
 * 
 * @author jukka.rahkonen@iki.fi
 */
public class JsonParser extends Jugile {

	private static Node root = null;
	private static Node node = null;

	public JsonParser() {

	}
	
	private static Node createNode(String tag) {
		if (root == null) {
			root = new Node(tag);
			node = root;
		} else {
			node = node.add(tag);
		}
		return node;
	}

	public Node getRoot() { return root; }
	
	
	// -------- parser ---------
	
	private enum State {
		CONTENT, STRING, NUM, LIST, TAG
	};


	public static void parse(String filename, String enc) {
		List<Token> tokens = getTokens(filename,enc);
		for (int i = 0; i < tokens.size(); i++) {
			Token t = tokens.get(i);
			if (t.type == Token.Type.START_LIST) {
				
			}
			if (t.type == Token.Type.START) {
				t = tokens.get(++i);
				if (t.type != Token.Type.STRING) fail("parse error: expected string name for node");
				print("node value: " + t.value);
				createNode(t.value);
				t = tokens.get(++i);
				if (t.type != Token.Type.COLON) fail("parse error: expected colon");
				t = tokens.get(++i);
				if (t.type == Token.Type.START) {
					i--;
					continue;
				}
				if (t.type == Token.Type.STRING) {
					node.value(t.value);
					node = node.parent();
					continue;
				}	
				continue;
			}
			if (t.type == Token.Type.COMMA) {
				t = tokens.get(++i);
				if (t.type != Token.Type.STRING) fail("parse error: expected string name for node");
				print("node value: " + t.value);
				createNode(t.value);
				t = tokens.get(++i);
				if (t.type != Token.Type.COLON) fail("parse error: expected colon");
				t = tokens.get(++i);
				if (t.type == Token.Type.START) {
					i--;
					continue;
				}
				if (t.type == Token.Type.STRING) {
					node.value(t.value);
					node = node.parent();
					continue;
				}	
				continue;
			}
			
			if (t.type == Token.Type.END) {
				if (node != null) node = node.parent();
			}
		}
		print(root.render());
	}

	// ---- tokenizer -----

	public static void main(String args[]) {		
		for (Token t : getTokens("data/foo.json","UTF8")) {
			print(t.toString());
		}
		parse("data/foo.json","UTF8");
	}
	
	public static class Token {
		public enum Type {
			START,
			END,
			STRING,
			NUM,
			START_LIST,
			END_LIST,
			COLON,
			COMMA,
			UTFCHAR,
		}
		
		private String value;
		private Type type;

		public String toString() {
			return "" + type + ": " + value;
		}
		
		public Token(Type type) {
			this.type = type;
		}
		
	}
	
	public static List<Token> getTokens(String filename, String enc) {
		print("read: " + filename);
		List<Token> tokens = new ArrayList<Token>();
		try {
			InputStream is = getResource(filename);
			InputStreamReader isr = new InputStreamReader(is, enc);
			BufferedReader in = new BufferedReader(isr);
			int ch;
			State st = State.CONTENT;
			StringBuffer buf = new StringBuffer();
			Token token = null;
			while ((ch = in.read()) >= 0) {
				if (st == State.CONTENT) {
					if (ch == '{') {
						tokens.add(new Token(Token.Type.START));
						continue;
					}
					if (ch == '}') {
						tokens.add(new Token(Token.Type.END));
						continue;
					}
					if (ch == '"') {
						st = State.STRING;
						token = new Token(Token.Type.STRING);
						buf = new StringBuffer();
						continue;
					}
					if (Character.isDigit(ch)) {
						st = State.NUM;
						token = new Token(Token.Type.NUM);
						buf = new StringBuffer();
						buf.append((char)ch);
						continue;						
					}
					if (ch == ',') {
						tokens.add(new Token(Token.Type.COMMA));
						continue;						
					}
					if (ch == ':') {
						tokens.add(new Token(Token.Type.COLON));
						continue;						
					}
					if (ch == '[') {
						tokens.add(new Token(Token.Type.START_LIST));
						continue;						
					}
					if (ch == ']') {
						tokens.add(new Token(Token.Type.END_LIST));
						continue;						
					}
					continue;
				}

				if (st == State.STRING) {
					if (ch == '"') {
						st = State.CONTENT;
						token.value = buf.toString();
						tokens.add(token);
						continue;
					}
					buf.append((char)ch);
					continue;
				}

				if (st == State.NUM) {
					if (Character.isWhitespace(ch)) {
						st = State.CONTENT;
						token.value = buf.toString();
						tokens.add(token);
						continue;
					}
					buf.append((char)ch);
					continue;
				}

			}
			in.close();
		} catch (Exception e) {fail(e);}
		return tokens;
	}
	
}
